using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Harmony.ILCopying
{
	[Flags]
	public enum Protection
	{
		PAGE_NOACCESS = 0x01,
		PAGE_READONLY = 0x02,
		PAGE_READWRITE = 0x04,
		PAGE_WRITECOPY = 0x08,
		PAGE_EXECUTE = 0x10,
		PAGE_EXECUTE_READ = 0x20,
		PAGE_EXECUTE_READWRITE = 0x40,
		PAGE_EXECUTE_WRITECOPY = 0x80,
		PAGE_GUARD = 0x100,
		PAGE_NOCACHE = 0x200,
		PAGE_WRITECOMBINE = 0x400
	}

	public static class Memory
	{
		private static readonly HashSet<PlatformID> WindowsPlatformIDSet = new HashSet<PlatformID>
		{
			PlatformID.Win32NT, PlatformID.Win32S, PlatformID.Win32Windows, PlatformID.WinCE
		};

		public static bool IsWindows => WindowsPlatformIDSet.Contains(Environment.OSVersion.Platform);
		// 安全使用 windows 引用，因为这只会在 windows 上调用
		//
		// Safe to use windows reference since this will only ever be called on windows
		//
		[DllImport("kernel32.dll")]
		public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, Protection flNewProtect, out Protection lpflOldProtect);
		/// <summary>
		/// 取消保护内存页
		/// </summary>
		/// <param name="memory"></param>
		public static void UnprotectMemoryPage(long memory)
		{
			if (IsWindows)
			{
				bool success = VirtualProtect(new IntPtr(memory), new UIntPtr(1), Protection.PAGE_EXECUTE_READWRITE, out var _ignored);
				if (success == false)
					throw new System.ComponentModel.Win32Exception();
			}
		}

		public static string DetourMethod(MethodBase original, MethodBase replacement)
		{
			Int64 originalCodeStart = GetMethodStart(original, out var exception);
			if (originalCodeStart == 0)
				return exception.Message;
			Int64 patchCodeStart = GetMethodStart(replacement, out exception);
			if (patchCodeStart == 0)
				return exception.Message;

			return WriteJump(originalCodeStart, patchCodeStart);
		}

		/*
		 * This is still a rough part in Harmony. So much information and no easy way
		 * to determine when and what is valid. Especially with different environments
		 * and .NET versions. More information might be found here:
		 * 这在Harmony中仍然是一个粗糙的部分。信息太多，没有简单的方法
		 * 确定何时以及什么有效。特别是在不同的环境中
		 * 和 .NET 版本。更多信息可以在这里找到：
		 *
		 * https://stackoverflow.com/questions/38782934/how-to-replace-the-pointer-to-the-overridden-virtual-method-in-the-pointer-of/
		 * https://stackoverflow.com/questions/39034018/how-to-replace-a-pointer-to-a-pointer-to-a-method-in-a-class-of-my-method-inheri
		 *
		 * Miao：我要看懂这一段跳跃做了什么！！！
		 */
		public static string WriteJump(long memory, long destination)
		{
			UnprotectMemoryPage(memory);
			
			if (IntPtr.Size == sizeof(long))//我们来看64的版本
			{
				//总之有可能不进入，不进入就不会加offset了
				if (CompareBytes(memory, new byte[] { 0xe9 }))//看看是不是e9指令 这个e9就是x64 x86汇编指令了，是被称为近跳转的汇编方法(Jmp(near))
				{
					//https://blog.csdn.net/wzsy/article/details/17163589
					//0xE9    JMP    后面的四个字节是偏移 按照这个人写的。
					int offset = ReadInt(memory + 1);//读了jmp指令后跟随的
					//那memory现在就等于 Jmp命令+偏移字节本身+偏移的地址，是1+4+offset
					memory += 5 + offset;
				}
				//http://ref.x86asm.net/coder64.html#xB8按照这个参照码如果没错的话
				//现在的memory是跳转地址后所处的位置
				//0x48 和 0xB8 = 两段组成指令组成合集，0x48指明为操作数是64位大小的
				//单字节指令：操作码、操作数同在一个字节中。双字节指令：操作码+操作数。
				//如果按照表上的没错的话 第一个指令是声明操作数是64位的，也就是destination，第二个B8的意思是Move后面需要跟r，这里r也由标记是可以跳转的操作数或者是立即数。
				memory = WriteBytes(memory, new byte[] { 0x48, 0xB8 });
				memory = WriteLong(memory, destination);
				//E0=递减计数；如果 count!=0 且 ZF=0 则短跳转 //Increment by 1增量增加0?
				//FF

				memory = WriteBytes(memory, new byte[] { 0xFF, 0xE0 });//0xFF, 0xE0 是 jmp eax 
			}
			else
			{
				memory = WriteByte(memory, 0x68);
				memory = WriteInt(memory, (int)destination);
				memory = WriteByte(memory, 0xc3);
			}
			return null;
		}
		/// <summary>
		/// 获得运行时句柄
		/// </summary>
		/// <param name="method"></param>
		/// <returns></returns>
		private static RuntimeMethodHandle GetRuntimeMethodHandle(MethodBase method)
		{
			if (method is DynamicMethod)
			{
				var nonPublicInstance = BindingFlags.NonPublic | BindingFlags.Instance;

				// DynamicMethod actually generates its m_methodHandle on-the-fly and therefore
				// we should call GetMethodDescriptor to force it to be created
				//DynamicMethod实际上会动态生成其m_methodHandle，因此
				//我们应该调用GetMethodDescriptor来强制创建它
				
				var m_GetMethodDescriptor = typeof(DynamicMethod).GetMethod("GetMethodDescriptor", nonPublicInstance);
				if (m_GetMethodDescriptor != null)
					return (RuntimeMethodHandle)m_GetMethodDescriptor.Invoke(method, new object[0]);

				// .Net Core
				var f_m_method = typeof(DynamicMethod).GetField("m_method", nonPublicInstance);
				if (f_m_method != null)
					return (RuntimeMethodHandle)f_m_method.GetValue(method);

				// Mono
				var f_mhandle = typeof(DynamicMethod).GetField("mhandle", nonPublicInstance);
				return (RuntimeMethodHandle)f_mhandle.GetValue(method);
			}

			return method.MethodHandle;
		}

		public static long GetMethodStart(MethodBase method, out Exception exception)
		{
			// required in .NET Core so that the method is JITed and the method start does not change
			// 在 .NET Core 中是必需的，这样方法是 JITed 并且方法 start 不会改变
			RuntimeMethodHandle handle = GetRuntimeMethodHandle(method);
			try
			{
				RuntimeHelpers.PrepareMethod(handle);//这段是必要的，好像是用来锁内存
			}
			catch (Exception)
			{
			}

			try
			{
				exception = null;
				return handle.GetFunctionPointer().ToInt64();//我也知道这就是获取函数指针了
			}
			catch (Exception ex)
			{
				exception = ex;
				return 0;
			}
		}
		/// <summary>
		/// 比较字节数
		/// </summary>
		/// <param name="memory"></param>
		/// <param name="values"></param>
		/// <returns></returns>
		public static unsafe bool CompareBytes(long memory, byte[] values)
		{
			byte* p = (byte*)memory;//将long转为取内存地址指向的内存（byte）
			foreach (var value in values)
			{
				if (value != *p)
				{
					return false;
				}
				p++;
			}
			return true;
		}

		public static unsafe byte ReadByte(long memory)
		{
			byte* p = (byte*)memory;
			return *p;
		}

		public static unsafe int ReadInt(long memory)
		{
			int* p = (int*)memory;
			return *p;//这里就是取指针下的数值了，现在的问题在于读的4个字节的操作码究竟是什么
		}

		public static unsafe long ReadLong(long memory)
		{
			var p = (long*)memory;
			return *p;
		}

		public static unsafe long WriteByte(long memory, byte value)
		{
			byte* p = (byte*)memory;
			*p = value;
			return memory + sizeof(byte);
		}

		public static unsafe long WriteBytes(long memory, byte[] values)
		{
			foreach (var value in values)
				memory = WriteByte(memory, value);
			return memory;
		}

		public static unsafe long WriteInt(long memory, int value)
		{
			var p = (int*)memory;
			*p = value;
			return memory + sizeof(int);
		}

		public static unsafe long WriteLong(long memory, long value)
		{
			var p = (long*)memory;
			*p = value;
			return memory + sizeof(long);
		}
	}
}
